Skip to content

Implement OAuth feature for Python SDK#5

Open
bettercallsaulj wants to merge 31 commits intomainfrom
iml_python_sdk_auth
Open

Implement OAuth feature for Python SDK#5
bettercallsaulj wants to merge 31 commits intomainfrom
iml_python_sdk_auth

Conversation

@bettercallsaulj
Copy link
Collaborator

No description provided.

RahulHere added 30 commits March 12, 2026 08:24
Create auth module structure with type definitions
for gopher-auth FFI bindings.

Changes:
- Create gopher_mcp_python/ffi/auth/ directory structure
- Add types.py with GopherAuthError enum (error codes -1000 to -1016)
- Add ERROR_DESCRIPTIONS dict with human-readable error messages
- Add ValidationResult dataclass (valid, error_code, error_message)
- Add TokenPayload dataclass (subject, scopes, audience, expiration, issuer)
- Add AuthContext dataclass (user_id, scopes, audience, token_expiry, authenticated)
- Add is_gopher_auth_error() helper function
- Add get_error_description() helper function
- Add create_empty_auth_context() factory function
- Add __init__.py with module exports
Create FFI loader module for loading the native gopher-orch library.

Changes:
- Add loader.py with ctypes-based FFI bindings
- Define opaque pointer types: GopherAuthClientPtr, GopherAuthPayloadPtr,
  GopherAuthOptionsPtr
- Define GopherAuthValidationResult ctypes Structure with valid, error_code,
  and error_message fields
- Implement _get_library_name() with platform detection (darwin/windows/linux)
- Implement _get_platform_package_path() for pip-distributed native packages
  with architecture detection (arm64/x64)
- Implement _get_search_paths() returning platform package, development paths
  (native/lib), and system paths (/usr/local/lib, /usr/lib)
- Implement load_library() with GOPHER_ORCH_LIBRARY_PATH environment variable
  support and debug logging
- Implement is_library_loaded() and get_library() functions
- Update __init__.py with loader exports
Switch submodule to dev_auth branch for auth FFI support.

Changes:
- Update .gitmodules branch from br_release to dev_auth
- Update submodule to commit 48ddecc4 (Resolve test timeouts
  and remove ambiguous addServer overload)
Add ctypes function bindings for all gopher_auth_* C API functions
with graceful handling when functions are not available in the library.

Changes:
- Add _FUNCTION_SPECS list with all function signatures
- Add _setup_functions() to configure argtypes and restype for each function
- Add _has_function() and _get_function() helpers for safe function access
- Add is_auth_available() to check if auth functions exist in library
- Add get_auth_functions() returning dict of available function references
- Handle missing functions gracefully (return None instead of raising)
- Update __init__.py with new exports

Function bindings added:
- Library lifecycle: auth_init, auth_shutdown, auth_version
- Client: client_create, client_destroy, client_set_option
- Options: options_create, options_destroy, options_set_scopes,
  options_set_audience, options_set_clock_skew
- Validation: validate_token, extract_payload
- Payload: payload_get_subject, payload_get_scopes, payload_get_audience,
  payload_get_expiration, payload_get_issuer, payload_destroy
- Utility: free_string, generate_www_authenticate, generate_www_authenticate_v2
Add ValidationOptions class with fluent API for configuring JWT
token validation parameters.

Changes:
- Add validation_options.py with ValidationOptions class
- Implement __init__() with native handle creation via options_create
- Add fluent setters: set_scopes(), set_audience(), set_clock_skew()
- Add lifecycle methods: get_handle(), destroy(), is_destroyed()
- Add _ensure_not_destroyed() helper for state validation
- Implement context manager protocol (__enter__, __exit__)
- Add __del__ destructor for resource cleanup
- Add create_validation_options() factory with defaults (clock_skew=30)
- Graceful error handling when auth functions unavailable
- Update __init__.py with ValidationOptions exports
Add AuthClient class and module-level functions for JWT token
validation using JWKS-based signature verification.

Changes:
- Add auth_client.py with AuthClient class
- Add init_auth_library() and shutdown_auth_library() lifecycle functions
- Add get_auth_library_version() and is_auth_library_initialized()
- Implement AuthClient.__init__() with JWKS URI and issuer configuration
- Add set_option() for cache_duration, auto_refresh, request_timeout
- Add validate_token() returning ValidationResult dataclass
- Add extract_payload() returning TokenPayload dataclass
- Add _get_payload_string() helper for string field extraction
- Add validate_and_extract() convenience method for combined operation
- Implement destroy(), is_destroyed(), context manager protocol
- Add generate_www_authenticate_header() for basic Bearer format
- Add generate_www_authenticate_header_v2() for RFC 9728 format
- Graceful error handling when auth functions unavailable
- Update __init__.py with AuthClient and module function exports
Complete auth module integration with top-level exports and
comprehensive unit tests for all auth components.

Changes:
- Update gopher_mcp_python/ffi/__init__.py to expose auth module
- Update gopher_mcp_python/__init__.py with auth re-exports:
  GopherAuthError, ValidationResult, TokenPayload, AuthContext,
  AuthClient, ValidationOptions, init_auth_library, shutdown_auth_library,
  is_auth_available
- Create tests/ffi/auth/ directory structure
- Add test_types.py: Test GopherAuthError enum values, is_gopher_auth_error(),
  get_error_description(), ERROR_DESCRIPTIONS, dataclass creation
- Add test_loader.py: Test _get_library_name() for each platform,
  _get_search_paths(), pointer types, load_library(), get_auth_functions()
- Add test_validation_options.py: Test fluent API, context manager,
  destroy() idempotency (skipped when auth unavailable)
- Add test_auth_client.py: Test init/shutdown lifecycle, context manager,
  error handling (skipped when auth unavailable)

Test results: 60 passed, 28 skipped (auth functions not in library)
Set up the py_auth_mcp_server example with Flask-based OAuth
authentication using gopher-orch FFI bindings.

Project structure:
- py_auth_mcp_server/middleware/ - OAuth middleware components
- py_auth_mcp_server/routes/ - API route handlers
- py_auth_mcp_server/tools/ - MCP tool implementations
- tests/ - Test suite
- lib/ - Native library directory

Configuration:
- pyproject.toml with Flask, flask-cors dependencies
- Development tools: pytest, black, ruff, mypy
- Configured linting and formatting rules

Changes:
- Add examples/auth/ directory structure
- Add pyproject.toml with project configuration
- Add requirements.txt for pip installation
- Add package __init__.py files
- Add lib/.gitkeep for native library placement
Implement configuration loading module with key=value file parsing
and validation for OAuth/OIDC settings.

AuthServerConfig dataclass fields:
- Server settings: host, port, server_url
- OAuth/IDP settings: auth_server_url, jwks_uri, issuer,
  client_id, client_secret, token_endpoint
- Direct OAuth URLs: oauth_authorize_url, oauth_token_url
- Scopes: allowed_scopes
- Cache settings: jwks_cache_duration, jwks_auto_refresh,
  request_timeout
- Auth bypass: auth_disabled

Features:
- parse_config_file() for key=value format with comments
- load_config_from_file() and load_config_from_default_location()
- build_config() with endpoint derivation from auth_server_url
- validate_required_fields() for auth-enabled mode
- create_default_config() factory for testing

Changes:
- Add py_auth_mcp_server/config.py
Implement Flask middleware for JWT token validation using
gopher-auth FFI bindings.

OAuthAuthMiddleware class features:
- before_request() handler for Flask integration
- extract_token() from Authorization header or query param
- requires_auth() with public/protected path detection
- Token validation via AuthClient with ValidationOptions
- WWW-Authenticate header generation (RFC 9470)
- CORS preflight handling for cross-origin requests
- Auth context attached to Flask g object

Helper functions:
- require_auth() decorator for route protection
- has_scope() for scope-based access control
- get_auth_context() to retrieve current context
Implement health check and OAuth 2.0 discovery endpoints
for the MCP server.

Health endpoint (routes/health.py):
- GET /health with status, timestamp, version, uptime
- HealthResponse dataclass
- register_health_routes() for Flask integration

OAuth endpoints (routes/oauth_endpoints.py):
- GET /.well-known/oauth-protected-resource (RFC 9728)
- GET /.well-known/oauth-protected-resource/mcp (MCP specific)
- GET /.well-known/oauth-authorization-server (RFC 8414)
- GET /.well-known/openid-configuration (OIDC discovery)
- GET /oauth/authorize (redirect to IdP)
- POST /oauth/register (RFC 7591 dynamic registration)
- OPTIONS handlers for CORS preflight
Implement JSON-RPC 2.0 handler for Model Context Protocol
with tool registration and invocation.

JSON-RPC types:
- JsonRpcRequest/Response dataclasses
- JsonRpcError with standard error codes
- JsonRpcErrorCode constants (PARSE_ERROR, INVALID_REQUEST,
  METHOD_NOT_FOUND, INVALID_PARAMS, INTERNAL_ERROR)

Tool system:
- ToolSpec with name, description, inputSchema
- ToolResult with content items (text/image/resource)
- ToolContentItem for result content
- ToolHandler callable type

Changes:
- Add routes/mcp_handler.py
- Update routes/__init__.py with exports
Implement example MCP tools demonstrating OAuth scope-based
access control, mirroring the C++ auth example.

Simulation helpers:
- get_condition_for_city() - deterministic weather condition
- get_temp_for_city() - deterministic temperature
- get_simulated_weather() - current weather data
- get_simulated_forecast() - 5-day forecast
- get_simulated_alerts() - regional weather alerts

Access control:
- has_scope() - check scope in space-separated string
- access_denied() - create error result for missing scope

Tools:
- get-weather: No authentication required
- get-forecast: Requires mcp:read scope
- get-weather-alerts: Requires mcp:admin scope
Implement application factory for the Auth MCP Server with
auth library initialization and route registration.

create_app() function:
- Load configuration from file or defaults
- Initialize gopher-auth library if auth enabled
- Create AuthClient with JWKS URI and issuer
- Set client options (cache_duration, auto_refresh, timeout)
- Create OAuthAuthMiddleware for token validation
- Register before_request handler for authentication
- Register health, OAuth, and MCP routes
- Register weather tools with scope-based access

cleanup_app() function:
- Destroy AuthClient instance
- Shutdown auth library
Implement command-line entry point and example configuration files
for the Auth MCP Server.

Entry point (__main__.py):
- print_banner() with server name
- print_endpoints() showing all available routes
- main() with argument parsing
- Config file loading (CLI arg, cwd, or package dir)
- Command-line overrides: --host, --port, --no-auth
- Signal handlers for graceful shutdown (SIGINT, SIGTERM)
- cleanup_app() called on shutdown

Package metadata (__init__.py):
- __version__ = "1.0.0"
- __author__ = "Gopher Security"
- Exports: create_app, cleanup_app, config functions

Configuration files:
- server.config: Development config with auth_disabled=true
- server.config.example: Documented template with all options
Set up pytest fixtures for the Auth MCP Server tests.

Configuration fixtures:
- sample_config: AuthServerConfig with auth_disabled=True
- app: Flask application configured for testing
- client: Flask test client for HTTP requests

Handler fixtures:
- mcp_handler: Fresh McpHandler instance

Auth context fixtures:
- mock_auth_context: Authenticated user with mcp:read scope
- mock_admin_auth_context: Admin user with mcp:admin scope
- mock_unauthenticated_context: Unauthenticated context
Create tests for configuration loading and OAuth middleware.

test_config.py - Configuration tests:
- TestParseConfigFile: key=value parsing, comments, whitespace
- TestBuildConfig: defaults, type conversion, server_url
- TestEndpointDerivation: jwks_uri, token_endpoint, issuer
- TestValidateRequiredFields: auth enabled/disabled validation
- TestLoadConfigFromFile: file loading, FileNotFoundError
- TestCreateDefaultConfig: factory function
- TestAuthServerConfigDataclass: __post_init__ behavior

test_oauth_auth.py - Middleware tests:
- TestExtractToken: header, query param, precedence, missing
- TestRequiresAuth: auth disabled, no client, public paths
- TestSendUnauthorized: response format verification
- TestHasScope: authenticated/unauthenticated checks
- TestIsAuthDisabled: config flag verification
- TestRequireAuthDecorator: decorator behavior
Create tests for MCP JSON-RPC handler and weather tools.

test_mcp_handler.py - Handler tests:
- TestJsonRpcTypes: error/response to_dict() methods
- TestToolTypes: ToolSpec, ToolContentItem, ToolResult
- TestMcpHandlerRegisterTool: register_tool(), @tool decorator
- TestMcpHandlerGetTools: empty state, registered tools
- TestMcpHandlerHandleRequest: initialize, tools/list, tools/call,
  ping, method not found, invalid request, invalid params

test_weather_tools.py - Weather tools tests:
- TestHasScope: present, not present, empty, partial match
- TestGetConditionForCity: valid condition, deterministic, offset
- TestGetTempForCity: valid range, deterministic, offset
- TestGetSimulatedWeather: all fields, ranges, deterministic
- TestGetSimulatedForecast: 5 days, day names, high > low
- TestGetSimulatedAlerts: structure, region in message
- TestAccessDenied: error result format, scope in message
Create end-to-end integration tests for the Auth MCP Server.

TestHealthEndpoint:
- GET /health returns 200, JSON, status ok, timestamp, uptime

TestOAuthProtectedResource:
- GET /.well-known/oauth-protected-resource returns metadata
- Has resource, authorization_servers, scopes_supported
- CORS headers present
- MCP-specific endpoint works

TestOpenIDConfiguration:
- GET /.well-known/openid-configuration returns OIDC metadata
- Has issuer, authorization_endpoint, token_endpoint

TestOAuthAuthorizationServer:
- GET /.well-known/oauth-authorization-server returns RFC 8414
- Has response_types_supported, grant_types_supported
Create comprehensive README for the Auth MCP Server example.

Documentation sections:
- Overview: OAuth 2.0 protected MCP server features
- Prerequisites: Python 3.10+, pip, native library
- Installation: pip install and library setup
- Building the Native Library: cmake instructions
- Configuration: auth disabled/enabled modes, all options
- Running the Server: development and Flask modes
- Testing: pytest commands
- API Endpoints: health, OAuth discovery, MCP tools
- Available Tools: get-weather, get-forecast, get-weather-alerts
- Authentication Flow: ASCII diagram
- Troubleshooting: common errors and solutions
- Project Structure: directory layout
Run black, ruff, and fix linting issues across all example server
code and tests.

Code formatting:
- Apply black formatting to all Python files
- Fix import sorting with ruff isort

Linting fixes:
- Remove unused imports
- Fix unused variable assignments
- Update imports to use collections.abc.Callable

Configuration updates:
- Update pyproject.toml ruff config to use lint section
- Fix deprecated ruff configuration format

Python 3.10+ compatibility:
- Add `from __future__ import annotations` for union type hints
- Required for `X | Y` syntax on Python versions < 3.10
Rename all auth-related exports to use consistent Gopher prefix for
better namespace clarity and API consistency.

Changes:
- AuthClient → GopherAuthClient
- ValidationOptions → GopherValidationOptions
- AuthContext → GopherAuthContext
- init_auth_library → gopher_init_auth_library
- shutdown_auth_library → gopher_shutdown_auth_library
- get_auth_library_version → gopher_get_auth_library_version
- is_auth_library_initialized → gopher_is_auth_library_initialized
- generate_www_authenticate_header → gopher_generate_www_authenticate_header
- generate_www_authenticate_header_v2 → gopher_generate_www_authenticate_header_v2
- create_empty_auth_context → gopher_create_empty_auth_context
- create_validation_options → gopher_create_validation_options
Update remaining files that were missed in the initial rename:
- gopher_mcp_python/__init__.py: Use GopherAuthContext, GopherValidationOptions
- tests/ffi/auth/test_types.py: Update GopherAuthContext imports and class names
- tests/ffi/auth/test_validation_options.py: Update GopherValidationOptions imports
- tests/ffi/auth/test_auth_client.py: Update GopherAuthClient and all gopher_ prefixed functions

All 111 tests now pass.
Print instructions at the end of build.sh for running the Auth MCP Server
example, including:
- How to install dependencies
- Development mode (--no-auth)
- Production mode (with OAuth)
- Sample curl commands to test endpoints
Add convenient shell script to run the auth example:
- Automatically installs dependencies if needed
- Copies native library from parent build if available
- Sets up PYTHONPATH correctly
- Defaults to --no-auth mode for easier development
- Includes --help with usage examples and test commands

Update build.sh to reference the new script.
JsonRpcError was a dataclass being used with raise/except but didn't
inherit from BaseException. Changed it to a proper Exception subclass
so it can be raised and caught correctly.

This fixes the TypeError when MCP Inspector connects:
"catching classes that do not inherit from BaseException is not allowed"
Changed run_example.sh to use server.config settings instead of
forcing --no-auth mode. Auth behavior is now controlled by the
auth_disabled setting in server.config.
Prepare release v0.1.2:
- Update pyproject.toml to version 0.1.2
- Update platform packages to version 0.1.2
- Update CHANGELOG.md: [Unreleased] -> [0.1.2] - 2026-03-12
The run_example.sh script now auto-detects platform and architecture
to install the correct platform-specific native library package from pip.

Changes:
- Update gopher-mcp-python version to >=0.1.2 in requirements.txt and pyproject.toml
- Add platform-specific native library optional dependencies to pyproject.toml
- Replace local native library copying with pip package auto-installation
- Support darwin/linux/windows on arm64/x64 architectures
The auth loader was looking for 'gopher_orch_{platform}_{arch}' but the
native package is named 'gopher_mcp_python_native_{platform}_{arch}'.

Bump version to 0.1.2.1.
The SDK version (0.1.2.1) may differ from gopher-orch version (0.1.2).
Added GOPHER_ORCH_VERSION env variable to specify which release to download.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant